﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using System.Reflection.Emit;

namespace Invini.Training
{
    public class DeferredEventAction : TrainingActionBase
    {
        private class ActiveDelegate
        {
            private object element;
            private EventInfo eventInfo;
            private Delegate eventHandler;

            public ActiveDelegate(object element, EventInfo eventInfo, Delegate eventHandler)
            {
                this.element = element;
                this.eventInfo = eventInfo;
                this.eventHandler = eventHandler;
            }
            public void Clear()
            {
                this.eventInfo.RemoveEventHandler(this.element, this.eventHandler);
            }
        }

        private List<ActiveDelegate> activeDelegates;

        public PathComponentsCollection Element
        {
            get;
            set;
        }
        public string EventName
        {
            get;
            set;
        }
        public DeferredActionSync Sync
        {
            get;
            set;
        }

        public DeferredEventAction()
        {
            this.Element = new PathComponentsCollection();
        }

        public override void Execute()
        {
            if (this.Element == null || this.EventName == null || this.Sync == null)
            {
                this.onCompleted();
                return;
            }

            IEnumerable<object> objects = this.Element.FindElements();
            if (objects.Count() == 0)
            {
                this.onCompleted();
                return;
            }

            this.activeDelegates = new List<ActiveDelegate>();
            this.Sync.ClearRequested += this.clearRequested;

            foreach (object obj in objects)
            {
                Type type = obj.GetType();
                EventInfo eventInfo = type.GetEvent(this.EventName);
                if (eventInfo == null)
                    continue;

                Type eventDelegateType = eventInfo.EventHandlerType;
                MethodInfo invokeDelegateMethod = eventDelegateType.GetMethod("Invoke");
                if (invokeDelegateMethod == null)
                    continue;

                Type eventDelegateReturnType = invokeDelegateMethod.ReturnType;
                if (eventDelegateReturnType != typeof(void))
                    continue;

                List<Type> eventDelegateParametersTypes = new List<Type>();
                eventDelegateParametersTypes.Add(this.GetType());
                foreach (ParameterInfo parameter in invokeDelegateMethod.GetParameters())
                    eventDelegateParametersTypes.Add(parameter.ParameterType);

                DynamicMethod dynamicMethod = new DynamicMethod("", eventDelegateReturnType,
                      eventDelegateParametersTypes.ToArray(),
                      typeof(DeferredEventAction));

                ILGenerator ilgen = dynamicMethod.GetILGenerator();

                MethodInfo eventOccuredMethod = this.GetType().GetMethod("eventOccured", BindingFlags.NonPublic | BindingFlags.InvokeMethod | BindingFlags.Instance);

                ilgen.Emit(OpCodes.Ldarg_0);
                ilgen.Emit(OpCodes.Call, eventOccuredMethod);
                ilgen.Emit(OpCodes.Ret);

                Delegate handler = dynamicMethod.CreateDelegate(eventDelegateType, this);
                eventInfo.AddEventHandler(obj, handler);

                this.activeDelegates.Add(new ActiveDelegate(obj, eventInfo, handler));
            }

            this.onCompleted();
        }

        public override void Clear()
        {
        }

        private void clearRequested(DeferredActionSync sync)
        {
            sync.ClearRequested -= this.clearRequested;

            if (this.activeDelegates != null)
            {
                foreach (ActiveDelegate activeDelegate in this.activeDelegates)
                    activeDelegate.Clear();

                this.activeDelegates = null;
            }
        }
        private void eventOccured()
        {
            if (this.Sync == null)
                return;

            this.Sync.NotifyOccured();
        }
    }
}
